package com.tn.rtdata.examples.stockticker.server.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.tn.rtdata.DataEvent;
import com.tn.rtdata.DataListener;
import com.tn.rtdata.util.ObservableMap;

/**
 * A specialization of <code>ConcurrentHashMap</code> that allows listeners to be added to the mutator methods.
 */
public class ConcurrentObservableMap<K, V> extends ConcurrentHashMap<K, V> implements ObservableMap<K, V>
{
  private List<DataListener<V>> dataListeners = new ArrayList<DataListener<V>>();

  /**
   * {@inheritDoc}
   */
  @Override
  public V put(K key, V value)
  {
    V previousValue = super.put(key, value);
    fireDataListeners(new DataEvent<V>(previousValue != null ? DataEvent.Type.UPDATE : DataEvent.Type.ADD, value, previousValue));
    return previousValue;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public V putIfAbsent(K key, V value)
  {
    V previousValue = super.putIfAbsent(key, value);
    fireDataListeners(new DataEvent<V>(previousValue != null ? DataEvent.Type.UPDATE : DataEvent.Type.ADD, value, previousValue));
    return previousValue;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void putAll(Map<? extends K, ? extends V> map)
  {
    for (K key : map.keySet())
    {
      put(key, map.get(key));
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public V remove(Object key)
  {
    V value = super.remove(key);

    if (value != null)
    {
      fireDataListeners(new DataEvent<V>(DataEvent.Type.DELETE, null, value));
    }

    return value;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean remove(Object key, Object value)
  {
    boolean result = super.remove(key, value);

    if (result)
    {
      //noinspection unchecked
      fireDataListeners(new DataEvent<V>(DataEvent.Type.DELETE, null, (V)value));
    }

    return result;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean replace(K key, V oldValue, V newValue)
  {
    boolean result = super.replace(key, oldValue, newValue);

    if (result)
    {
      fireDataListeners(new DataEvent<V>(DataEvent.Type.UPDATE, newValue, oldValue));
    }

    return result;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public V replace(K key, V value)
  {
    V previousValue = super.replace(key, value);
    fireDataListeners(new DataEvent<V>(previousValue != null ? DataEvent.Type.UPDATE : DataEvent.Type.ADD, value, previousValue));
    return previousValue;
  }

  @Override
  public void clear()
  {
    Map<K, V> map = new HashMap<K, V>(this);

    super.clear();

    for (K key : map.keySet())
    {
      fireDataListeners(new DataEvent<V>(DataEvent.Type.DELETE, null, map.get(key)));
    }
  }

  /**
   * Adds the <code>dataListener</code>.
   */
  public void addDataListener(DataListener<V> dataListener)
  {
    dataListeners.add(dataListener);
  }

  /**
   * Adds the <code>dataListener</code>.
   */
  public void removeDataListener(DataListener<V> dataListener)
  {
    dataListeners.remove(dataListener);
  }

  /**
   * Fires the <code>dataListeners</code>.
   */
  private void fireDataListeners(DataEvent<V> dataEvent)
  {
    for (DataListener<V> dataListener : new ArrayList<DataListener<V>>(dataListeners))
    {
      dataListener.dataEvent(dataEvent);
    }
  }
}
